using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;

namespace Stunt.Nutss.Stunt
{

	/// <summary>Implements the STUNT TCP NAT Traversal protocol. The class operates in two
	/// modes -- passive and active, abbreviated PE and AE respectively. PE and AE
	/// coordinate over a signaling channel. A passive endpoint waits for an active
	/// endpoint to initiate a connection. The STUNT state machine is as follows.
	/// <p>Hole Punching:
	/// <ol>
	/// <li>AE: Registers with directory server
	/// <li>AE: Connects to STUNT responders #1 and #2 from the local port (AIP)
	/// and learns the external IP address and ports.
	/// <li>AE: listen() on AIP, sends INVITE to PE with predicted port (AEP)
	/// <li>PE: connect() to AEP, Connects to STUNT responders #1, #2 from local port (PIP)
	/// <li>PE: if connect() to AEP succeeds, jump to verification below
	/// <li>PE: listen() on PIP, sends ACCEPT to AE with predicted port (PEP)
	/// <li>AE: connect() to PEP, wait for timeout
	/// <li>AE: if connect() to PEP succeeds, jump to verification below
	/// <li>AE: On timeout, listen() on AIP, sends CALLBACK to PE
	/// <li>PE: connect() to AEP, wait for timeout
	/// <li>PE: if connect() to AEP succeeds, jump to verification below
	/// <li>PE: On timeout, sends SWAP to AE
	/// <li>AE: sends SWAP to PE, switches role and becomes PE
	/// <li>PE: switches role and becomes AE
	/// </ol>
	/// 
	/// <p>Verification:
	/// <ol>
	/// <li>AE, PE: write random nonce
	/// <li>AE, PE: read peer's nonce, XOR with shared secret, write it back to the socket
	/// <li>AE, PE: verify nonce read from socket is original nonce XOR shared secret
	/// <li>AE, PE: if verification fails, jump back to SWAP states above
	/// </ol>
	internal class STUNTCont : IContinuation
	{

		#region InnerClass: State

		/// <summary>
		/// States for STUNT state machine. No blocking or lengthy operations in any state. State transisions:
		/// <img src="doc-files/STUNTCont.State-1.gif">
		/// </summary>
		internal class State
		{

			#region Constants

			/// <summary>Start state for Active Endpoint (AE)</summary>
			static public State A_START = new State(0);
			/// <summary>
			/// AE: Awaiting response from STUNT Responder 1. Registration succeeded,
			/// connect() to responder 1 initialized.
			/// </summary>
			static public State A_ST1 = new State(T_EXPIRE);
			/// <summary>
			/// AE: Awaiting response from STUNT Responder 2. STUNT Responder 1 responded,
			/// connect() to responder 2 initialized.
			/// </summary>
			static public State A_ST2 = new State(T_EXPIRE);
			/// <summary>
			/// AE: Awaiting direct connect or ACCEPT. STUNT responder 2 responded; port prediction completed.
			/// Remote endpoint has been INVITE'ed. Server socket listening incase remote endpoint can connect
			/// directly i.e. NAT is EF=I. Otherwise, expecting remote endpoint to ACCEPT connection and
			/// predict its external port.
			/// </summary>
			static public State A_INV_SENT = new State(T_RETRY, 3);
			/// <summary>
			/// AE: Awaiting direct connect or timeout. Remote endpoint sent its external port prediction.
			/// direct connect to it initiated in case its NAT is EF=I. Connection will timeout if not.
			/// </summary>
			static public State A_INV_ACKD = new State(T_SHORT);
			/// <summary>
			/// AE: Awaiting direct connect. Connect attempt timeout, but hole has been punched. Remote endpoint
			/// should be able to connect now. Send CALLBACK request.
			/// </summary>
			static public State A_CB_SENT = new State(T_RETRY, T_EXPIRE / T_RETRY); // P_CB_RECV will wait for T_EXPIRE
			/// <summary>
			/// AE: Awaiting remote nonce. Direct connect succeeded. Sent nonce for verification stage.
			/// Awaiting remote endpoints nonce for echo service.
			/// </summary>
			static public State A_ECHO = new State(T_RETRY);
			/// <summary>
			/// AE: Awaiting nonce response. Remote nonce received, transformed and echoed back.
			/// </summary>
			static public State A_DONE = new State(T_RETRY);
			/// <summary>
			/// AE: Awaiting invite. Last attempt was aborted.
			/// Assumed role of passive endpoint. SWAP request sent.
			/// </summary>
			static public State A_SWAP = new State(T_RETRY, 3);
			/// <summary>Starting state for Passive Endpoint (PE)</summary>
			static public State P_START = new State(0);
			/// <summary>
			/// PE: Awaiting direct connect or response from STUNT Responder 1. INVITE with remote port prediction received.
			/// direct connect() started, connect() to responder 1 initialized.
			/// </summary>
			static public State P_ST1 = new State(T_EXPIRE);            // Connect to STUNT Responder 1
			/// <summary>
			/// PE: Awaiting response from STUNT Responder 2. STUNT Responder 1 responded,
			/// connect() to responder 2 initialized.
			/// </summary>
			static public State P_ST2 = new State(T_EXPIRE);            // Connect to STUNT Responder 2
			/// <summary>
			/// PE: Awaiting direct connect or CALLBACK. STUNT responder 2 responded; port prediction completed.
			/// Remote endpoint has been ACCEPT'ed. Server socket listening incase remote endpoint can connect
			/// directly i.e. NAT is EF=I. Otherwise, expecting remote endpoint to request a CALLBACK
			/// </summary>
			static public State P_INV_ACKD = new State(T_EXPIRE, 3);     // STUNT Invite Accepted, Expect ST_ESTD, ST_CB
			/// <summary>
			/// PE: Awaiting direct connect. Remote endpoint requested CALLBACK. connect() to
			/// remote endpoint started
			/// </summary>
			static public State P_CB_RCVD = new State(T_EXPIRE);        // STUNT Callback Received, Expect connect_success
			/// <summary>
			/// PE: Awaiting remote nonce. Direct connect succeeded. Sent nonce for verification stage.
			/// Awaiting remote endpoints nonce for echo service.
			/// </summary>
			static public State P_ECHO = new State(T_RETRY);            // Echo Request Sent
			/// <summary>PE: Awaiting nonce response. Remote nonce received, transformed and echoed back.</summary>
			static public State P_DONE = new State(T_RETRY);            // Established
			/// <summary>
			/// PE: Awaiting SWAP. Last attempt was aborted. Assumed role of active endpoint. SWAP request sent.
			/// Waiting for remote endpoint to acknowledge it has swapped roles.
			/// </summary>
			static public State P_SWAP = new State(T_RETRY, 3);          // Swap roles
			/// <summary>
			/// STUNT protocol terminated. If verification succeeded, then socket is connected. Otherwise,
			/// attempt was aborted either due to an unrecoverable error, or after trying the connection
			/// attempt a number of times and giving up.
			/// </summary>
			static public State STOP = new State(-1);

			/// <summary>Timeout between retries for a given state in milliseconds.</summary>
			private int timeout;

			/// <summary>Number of times the last signaling message should be resent when a state timeout expires.</summary>
			private int retries;

			internal int Value = GValue++;
			static int GValue = 1;

			#endregion

			#region Constructor

			/// <summary>
			/// Creates a new state with the given timeout and retries counter
			/// </summary>
			/// <param name="timeout">timeout between retries in milliseconds</param>
			/// <param name="retries">number of retries before aborting</param>
			State(int timeout, int retries)
			{
				this.timeout = timeout;
				this.retries = retries;
			}
			/// <summary>
			/// Creates a new state with the given timer and a default retry counter of 1.
			/// </summary>
			/// <param name="timeout">timeout between retries in milliseconds</param>
			State(int timeout)
				: this(timeout, 1)
			{
			}

			#endregion

			#region Properties

			/// <summary>Returns the timeout for the state in milliseconds.</summary>
			internal int Timeout
			{
				get
				{
					return timeout;
				}
			}

			/// <summary>Returns the number of retries for the state.</summary>
			internal int Retries
			{
				get
				{
					return retries;
				}
			}

			#endregion

		}

		#endregion

		#region Variables

		/// <summary>Magic bits for verifying STUNT connecctions</summary>
		private static int STUNT_MAGIC = 0x5ca1ab1e;          // ;-)

		/// <summary>Retry timer for resending signaling messages, and minor TCP timeouts in milliseconds.</summary>
		private static int T_RETRY = 1000;

		/// <summary>Long retry timer, mostly for TCP related timeouts in milliseconds.</summary>
		private static int T_EXPIRE = 4000;

		/// <summary>Short timer for preempting TCP in milliseconds.</summary>
		private static int T_SHORT = 500;

		/// <summary>
		/// Number of times to try the connection. Each try involves one run of the STUNT protocol
		/// as an Active Endpoint and one run as a Passive Endpoint.
		/// </summary>
		private static int MAX_ATTEMPTS = 1;

		/// <summary>Exception to hand to user function.</summary>
		private Exception reason = null;

		/// <summary>Whether operating as an Active Endpoint or not.</summary>
		private bool isActive = false;

		/// <summary>Current state of the local endpoint.</summary>
		private State state;

		/// <summary>Number of times the last signalig packet has been transmitted</summary>
		private int tries;

		/// <summary>ID for the local endpoint. Usually userdefined as a UID or email address or some sort.</summary>
		private NutssUri id;

		/// <summary>ID for the remote endpoint. Usually userdefined as a UID or email address or some sort.</summary>
		internal NutssUri dst;

		/// <summary>signaling channel</summary>
		private ISignalingContext sig;

		/// <summary>Address of signaling proxy</summary>
		private IPEndPoint proxyAddress;

		/// <summary>
		/// Addresses of STUNT responders. Responders echo back the source IP address and port
		/// of a TCP connection made to them as observed by them (i.e. external NAT address and port).
		/// </summary>
		private IPEndPoint[] responders;

		/// <summary>Signaling packet used to communicate with remote endpoint.</summary>
		private ISignalingMessage msg;

		/// <summary>TCP socket used during connection establishment.</summary>
		private Socket asock;

		/// <summary>TCP socket used during connection establishment.</summary>
		private Socket bsock;

		/// <summary>TCP socket for the final established connection.</summary>
		private Socket fsock;

		/// <summary>
		/// IP address and port responses from the two STUNT servers. Used for predicting
		/// the next NAT'ed address and port.
		/// </summary>
		private IPEndPoint[] eaddrs = new IPEndPoint[2];

		/// <summary>Local address corresponding to the predicted external address.</summary>
		private IPEndPoint iaddr;

		/// <summary>TCP server socket to accept incoming connections.</summary>
		private Socket psock;

		/// <summary>STUNT protocol data to be Send to remote host. Sent as the payload of the signaling packet.</summary>
		private STUNTMessage stuntState = new STUNTMessage();

		/// <summary>STUNT protocol data from the remote host. Received from the payload of the signaling packet.</summary>
		private STUNTMessage peerState;

		/// <summary>Random nonce used for verification of full-duplex'ness of established TCP stream.</summary>
		private int nonce;

		/// <summary>
		/// Number of times the endpoint has switched to of from Active mode. A full connection attempt
		/// requires two swaps. The number of attempts is governed by MAX_ATTEMPTS.
		/// </summary>
		private int swapped;
		/// <summary>Buffer to write an integer during connection verification.</summary>
		byte[] outbuf = new byte[4];
		/// <summary>Buffer to read an integer during connection verification.</summary>
		byte[] inbuf = new byte[4];
		/// <summary>Whether or not a verified TCP connection has been established between the two endpoints.</summary>
		bool established;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructs an unconnected STUNT endpoint.
		/// </summary>
		/// <param name="id">IP of local endpoint</param>
		/// <param name="context">signaling channel</param>
		/// <param name="responders">IP addresses and ports of STUNT responders</param>
		/// <param> dst ID of remote endpoint
		internal STUNTCont(NutssUri dst, NutssUri id, ISignalingContext context, IPEndPoint[] responders)
		{
			this.sig = context;
			this.dst = dst;
			this.id = id;
			this.responders = responders;
		}

		#endregion

		#region Init

		/// <summary>Initializes a STUNT endpoint.</summary>
		/// <param name="msg">If null, endpoint becomes an Active Endpoint. Otherwise, message must be a STUNT INVITE message</param>
		/// <returns>signaling message to match against</returns>
		public ISignalingMessage Init(ISignalingMessage msg)
		{

			if (msg == null)
			{              // a connect() was initiated
				state = State.A_START;
				this.msg = sig.CreateMessage(id, dst, stuntState);
				isActive = true;
				return this.msg;
			}

			else
			{                        // an incoming INVITE was received
				state = State.P_START;
				peerState = (STUNTMessage)msg.Payload;
				this.msg = sig.CreateReply(msg, stuntState);
				isActive = false;
				return this.msg;
			}
		}

		#endregion

		#region ReSend

		/// <summary>Resends the last signaling packet unless the retry limit of the current state has been exceeded.</summary>
		/// <returns>true if the packet was sent, false otherwise</returns>
		private bool ReSend()
		{

			if (tries >= state.Retries)
				return false;
			tries++;
			sig.Send(msg);
			return true;
		}

		#endregion

		#region Send

		/// <summary>Sends a signaling packet. Resets the retry counter.</summary>
		/// <param name="payload">payload to Send</param>
		private void Send(STUNTMessage payload)
		{
			tries = 0;
			msg = sig.SetMessage(msg, payload);
			ReSend();
		}

		#endregion

		#region IsConnected

		/// <summary>Attempts to complete a non-blocking connect.</summary>
		/// <param name="sock">Socket on which a non-blocking connect was previously called.</param>
		/// <returns>true if the connect() succeeded, false otherwise</returns>
		private bool IsConnected(Socket sock)
		{

			try
			{
				////sock.Shutdown(SocketShutdown.Both);
				//sock.finishConnect(); // wait for the end of non blocking connect
				return true;
			}

			catch (Exception e)
			{
				return false;
			}
		}

		#endregion

		#region GetNBConnSock

		/// <summary>Initiates a non-blocking connect on a TCP socket.</summary>
		/// <param name="local">Local IP address and port to bind to. null to bind to any available address.</param>
		/// <param name="remote">Remote IP address and port to connect to.</param>
		/// <param name="sel">Selector that will receive connect() completion events.</param>
		/// <returns>a newly created Socket set to non-blocking connect to given destination</returns>
		private Socket GetNBConnSock(IPEndPoint local, IPEndPoint remote)
		{
			Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);

			if (local != null)
				sock.Bind(local);
			sock.Blocking = false;
			SocketAttachments.Add(sock.Handle.ToInt64(), this);
			sock.BeginConnect(remote, null, null);
			return sock;
		}

		#endregion

		#region GetNBLstnSock

		/// <summary>Initiates a non-blocking accept on a TCP server socket.</summary>
		/// <param name="local"><Local IP address and port to bind to./param>
		/// <param name="sel">Selector that will receive accept() events.</param>
		/// <returns>a newly created ServerrSocketChannel set to non-blocking accept incoming connections</returns>
		private Socket GetNBLstnSock(IPEndPoint local)
		{
			Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
			sock.Bind(local);
			sock.Blocking = false;
			SocketAttachments.Add(sock.Handle.ToInt64(), this);
			return sock.Accept();
		}

		#endregion

		#region Predict

		/// <summary>
		/// Predicts the external address. Prediction is based on responses from STUNT responders. If
		/// two back-to-back TCP streams to two STUNT responders received external mapping (A1,P1), (A2,P2)
		/// then predicted mapping is (A1,P2+(P2-P1)).
		/// </summary>
		/// <returns>predicted external mapping</returns>
		private IPEndPoint Predict()
		{
			return new IPEndPoint(eaddrs[0].Address, 2 * eaddrs[1].Port - eaddrs[0].Port);
		}

		#endregion

		#region ReadOne

		/// <summary>Reads one Object from the TCP stream and closes it</summary>
		/// <param name="sock">TCP stream to read from</param>
		/// <returns>object read</returns>
		private object ReadOne(Socket sock)
		{
			sock.Blocking = true;
			BinaryFormatter formatter = new BinaryFormatter();
			object o = formatter.Deserialize(new NetworkStream(sock));
			sock.Close();
			return o;
		}

		#endregion

		#region ReadInt

		/// <summary>Reads one integer from a TCP stream</summary>
		/// <param name="sock">sock stream</param>
		/// <returns>integer read</returns>
		private int ReadInt(Socket sock)
		{

			try
			{
				byte[] buf = new byte[4];
				sock.Receive(buf);
				return BitConverter.ToInt32(buf, 0);
			}

			catch (Exception e)
			{
				Abort(e);                   // Established end-to-end TCP socket is in a wierd state
				return -1;
			}
		}

		#endregion

		#region WriteInt

		/// <summary>Writes one integer to a TCP stream</summary>
		/// <param name="sock">stream</param>
		/// <param name="val">integer to write</param>
		private void WriteInt(Socket sock, int val)
		{

			try
			{
				sock.Send(BitConverter.GetBytes(val));
			}

			catch (Exception e)
			{
				Abort(e);                   // Established end-to-end TCP socket is in a wierd state
			}
		}

		#endregion

		#region VerifyConnected

		/// <summary>Begins verification process of a established TCP stream.</summary>
		/// <param name="fsock">Stream to verify</param>
		/// <param name="sel">selector that will receive read() events</param>
		private void VerifyConnected(Socket fsock)
		{
			nonce = (int)(new Random().NextDouble() * Int32.MaxValue);
			WriteInt(fsock, nonce);

			if (fsock.Connected)
			{
				// when fsock was a client TCP socket on which a non-blocking connect() was called
				//fsock.keyFor(sel).interestOps(SelectionKey.OP_READ);
			}

			else
			{
				// when fsock was accept()'ed from a TCP server socket
				fsock.Blocking = false;
				//SelectionKey sockKey = fsock.register(sel, SelectionKey.OP_READ);
				SocketAttachments.Add(fsock.Handle.ToInt32(), this);
			}
		}

		#endregion

		#region ServerCloseHack

		/// <summary>
		/// Closes a server socket.
		/// </summary>
		/// <param name="sock"></param>
		private void ServerCloseHack(Socket sock)
		{
			sock.Close();
		}

		#endregion

		#region Reset

		/// <summary>Resets the endpoint state.</summary>
		private void Reset()
		{

			try
			{

				if (psock != null) { psock.Close(); psock = null; }

				if (asock != null) { asock.Close(); asock = null; }

				if (bsock != null) { bsock.Close(); bsock = null; }

				if (fsock != null) { fsock.Close(); fsock = null; }
				peerState = null;
			}

			catch (Exception e)
			{
				Debug.WriteLine("STUNTCont - Reset" + e.Message);
			}
		}

		#endregion

		#region AbortFatal

		/// <summary>Abort the connection setup.</summary>
		/// <param name="e">Exception to report to user</param>
		private void AbortFatal(Exception e)
		{
			reason = e;
			Debug.WriteLine("STUNTCont - AbortFatal" + e.Message);
			throw new Exception("STUNT attempt aborted.");
		}

		#endregion

		#region Abort

		/// <summary>
		/// Processes an error during the connection setup. It may result in the connection
		/// attempt being restarted with swapped roles, or may Abort the connection attempt
		/// if it has been retried enough times.
		/// </summary>
		/// <param name="e">Exception to report to user</param>
		private void Abort(Exception e)
		{
			reason = e;
			Debug.WriteLine("STUNTCont - Abort" + e.Message);
			throw new InvalidOperationException("STUNT attempt aborted.");
		}

		#endregion

		#region HandleTimeout

		/// <summary>Processes a timeout event.</summary>
		/// <param name="sel">Selector where socket keys are registered</param>
		private void HandleTimeout()
		{

			if (state == STUNTCont.State.A_START)
			{
				asock = GetNBConnSock(null, responders[0]);
				state = State.A_ST1;
				Debug.WriteLine("Attempting first STUNT lookup.");
			}

			else

				if (state == STUNTCont.State.P_START)
				{
					bsock = GetNBConnSock(null, peerState.pred);
					asock = GetNBConnSock(null, responders[0]);
					state = State.P_ST1;
					Debug.WriteLine("Passive client attemting first STUNT lookup and direct connect.");
				}

				else

					if (state == STUNTCont.State.A_INV_SENT)
					{

						if (!ReSend())
							Abort(new ExtendedSocketException("Connect Timeout"));
					}

			if (state == STUNTCont.State.P_CB_RCVD)
			{
				Abort(new ExtendedSocketException("Connect Timeout"));
			}

			else

				if (state == STUNTCont.State.P_INV_ACKD ||
					state == STUNTCont.State.A_CB_SENT ||
					state == STUNTCont.State.A_SWAP ||
					state == STUNTCont.State.P_SWAP)
				{

					if (!ReSend()) Abort(new ExtendedSocketException("Listen Timeout"));
				}

				else

					if (state == STUNTCont.State.A_INV_ACKD)
					{

						// FIXME: If the firewall is local, it knows when we close a client
						// socket and replace it with a server socket on the same port, and
						// blocks the incoming SYN. In that case, we can keep using the same
						// socket and hope to trigger a TCP simultaneous open. Windows XP
						// prior to SP2 doesn't support TCP simultaneous open, so this is
						// commented out by default. It may be possible to detect this behavior
						// at startup.
						//if (false) {          // Uncomment for local firewalls
						if (bsock != null)
						{
							bsock.Close();
							bsock = null;
						}
						psock = GetNBLstnSock(iaddr);
						//}
						state = State.A_CB_SENT;
						Send(stuntState.Callback());
						Debug.WriteLine("Direct connect timed out. Sending callback request.");
					}

					else
					{
						Debug.WriteLine("Unhandled timeout in state: " + state);
					}
		}

		#endregion

		#region HandleSocketOp

		/// <summary>Processes a socket event.</summary>
		/// <param name="key">Key for the event to handle</param>
		/// <param name="sel">Selector where socket keys are registered</param>
		void HandleSocketOp(Socket key)
		{

			if (state == STUNTCont.State.P_ST1 ||             // bsock is oppertunistic direct connect
				state == STUNTCont.State.P_ST2 ||             // - do -
				state == STUNTCont.State.A_INV_ACKD ||        // - do -
				state == STUNTCont.State.P_CB_RCVD)         // bsock is real last hope of connection
			{

				if (key == bsock)
				{

					if (IsConnected(bsock))
					{       // A connect() succeeded
						fsock = bsock;
						bsock = null;

						if (asock != null)
						{
							asock.Close();
							asock = null;
						}
						Send(stuntState.Established());
						VerifyConnected(fsock);
						state = isActive ? State.A_ECHO : State.P_ECHO;
						Debug.WriteLine("Direct connect succeeded. Verifying.");
					}

					else
					{
						key.Close(); //key.cancel();
						bsock.Close();
						bsock = null;
						Debug.WriteLine("Direct connect failed.");

						if (state == State.P_CB_RCVD)  // can recover from rest
							Abort(new ExtendedSocketException("Connect Timeout"));
					}
					return;
				}
			}

			else if (state == STUNTCont.State.P_ST1 || state == STUNTCont.State.A_ST1)
			{

				if (key == asock)
				{

					if (IsConnected(asock))
					{       // got response from STUNT server #1
						key.Close(); //key.cancel();
						iaddr = (IPEndPoint)asock.LocalEndPoint;
						eaddrs[0] = (IPEndPoint)ReadOne(asock);
						asock = GetNBConnSock(iaddr, responders[1]);
						state = isActive ? State.A_ST2 : State.P_ST2;
						Debug.WriteLine("First STUNT lookup successful(" + iaddr + "->" +
								eaddrs[0] + "), attemting second STUNT lookup.");
					}

					else
						Abort(new ExtendedSocketException("Failed contacting STUNT responder #1"));
				}
			}

			else if (state == STUNTCont.State.P_ST2 || state == STUNTCont.State.A_ST2)
			{

				if (key == asock)
				{

					if (IsConnected(asock))
					{
						// got response from STUNT server #2
						key.Close(); //key.cancel();
						eaddrs[1] = (IPEndPoint)ReadOne(asock);
						asock = null;
						psock = GetNBLstnSock(iaddr);
						Debug.WriteLine("Second STUNT lookup successful(" + iaddr + "->" + eaddrs[1] + ").");

						if (isActive)
						{
							state = State.A_INV_SENT;
							Send(stuntState.Invite(Predict()));
							Debug.WriteLine("Active client inviting passive, listening for direct connect.");
						}

						else
						{
							state = State.P_INV_ACKD;
							Send(stuntState.Accept(Predict()));
							Debug.WriteLine("Passive client accepting inviting, listening for direct connect.");
						}
					}

					else
						Abort(new ExtendedSocketException("Failed contacting STUNT responder #2"));
				}
			}

			else if (state == STUNTCont.State.A_CB_SENT ||
					state == STUNTCont.State.P_INV_ACKD ||
					state == STUNTCont.State.A_INV_SENT)
			{

				if (key == psock)
				{
					// the TCP server socket that was listen()'ing accepted the peer's direct
					// connect. As per the protocol, the peer that called connect() will Send
					// the STUNT_ESTABLISHED message -- i.e. the STUNT_ESTABLISHED message should
					// arrive 2xRTT later (1xRTT for the SYNACK to go back, 1xRTT for the message
					// to arrive). In some corner cases (where both endpoints are on the same
					// box, this may not hold) and the message may have arrived before the accept()
					// is signaled. That will cause a perfectly usable connection to be aborted
					// after a timeout. There is no attempt to address this since it is a very
					// rare case.
					key.Close(); //key.cancel();
					fsock = psock.Accept();
					Debug.WriteLine("Accepted direct connect, waiting for verification request.");
					psock.Close();
				}
			}

			else if (state == STUNTCont.State.P_ECHO || state == STUNTCont.State.A_ECHO)
			{

				if (key == fsock)
				{
					// check to make sure we connected to who we thought we connected to
					WriteInt(fsock, ReadInt(fsock) ^ STUNT_MAGIC ^ id.GetHashCode());
					state = isActive ? State.A_DONE : State.P_DONE;
					Debug.WriteLine("Echoed peer's half-pipe check, checking half-pipe.");
				}
			}

			else if (state == STUNTCont.State.P_DONE || state == STUNTCont.State.A_DONE)
			{

				if (key == fsock)
				{
					key.Close(); //key.cancel();

					if ((ReadInt(fsock) ^ STUNT_MAGIC ^ dst.GetHashCode()) == nonce)
					{
						state = State.STOP;
						established = true;
						Debug.WriteLine("Connection is full-pipe and verified. Success!!!");
					}

					else
						Abort(new ProtocolViolationException("Connection verification failed."));
				}
			}

			else
			{
				// Ideally, this should not be triggered. But there are subtle timing
				// corner cases here and there. So just log these occurances.
				Debug.WriteLine("Unhandled socket event: " + key);
				key.Close(); //key.cancel();
				//Abort(new UnsupportedOperationException("Not implemented."));
			}
		}

		#endregion

		#region HandleProxyMsg

		/// <summary>Handles a signaling message receive event.</summary>
		/// <param name="msg">Signaling message received</param>
		/// <param name="sel">Selector where socket keys are registered</param>
		void HandleProxyMsg(ISignalingMessage msg)
		{
			STUNTMessage msgState = msg.HasPayload ? (STUNTMessage)msg.Payload : null;

			if (msgState != null && msgState.stage == STUNTMessage.Stage.STUNT_ABORT)
			{
				// If remote endpoint requested a fatal ABORT, do it.
				Reset();
				state = State.STOP;
				AbortFatal(new ExtendedSocketException("Peer aborted connection attempt."));
				return;
			}

			if (msgState != null && msgState.stage == STUNTMessage.Stage.STUNT_SWAP)
			{

				// Handle a swap received at any point during the protocol. Such
				// a message implies that we are out of sync (except if we are also
				// in a SWAP state).
				if (state == State.A_SWAP || state == State.P_SWAP)
				{
					Reset();

					if (++swapped > 2 * MAX_ATTEMPTS - 1)
					{
						Send(stuntState.Abort());
						state = State.STOP;
					}

					else
					{
						Send(stuntState.Swap());
						state = isActive ? State.A_SWAP : State.P_SWAP;
					}
					// fallthrough -- allow P_SWAP to use this swap message that came in
				}

				else if (state == State.A_INV_SENT || state == State.P_INV_ACKD)
				{

					if (msgState != null && msgState.stage == STUNTMessage.Stage.STUN_ESTABLISHED)
					{

						if (fsock != null)
						{
							VerifyConnected(fsock);
							state = isActive ? State.A_ECHO : State.P_ECHO;
							Debug.WriteLine("Received verification request. Verifying.");
						}

						else
						{
							Abort(new ProtocolViolationException("Peer out of sync"));
						}
					}

					else if (msgState != null && msgState.stage == (isActive ? STUNTMessage.Stage.STUNT_ACCEPT : STUNTMessage.Stage.STUNT_CALLBACK))
					{

						if (psock != null)
						{
							ServerCloseHack(psock);
							psock = null;
						}

						if (msgState.pred != null) peerState = msgState;
						bsock = GetNBConnSock(iaddr, peerState.pred);
						state = isActive ? State.A_INV_ACKD : State.P_CB_RCVD;
						Debug.WriteLine("Passive client accepted invite, trying direct connect.");
					}

					else if (msg.Error)
					{
						AbortFatal(new ExtendedSocketException("Unknown ID"));
					}
				}

				if (state == State.A_CB_SENT)
				{

					if (msgState != null && msgState.stage == STUNTMessage.Stage.STUNT_ESTABLISHED)
					{

						if (fsock != null)
						{
							VerifyConnected(fsock);
							state = State.A_ECHO;
							Debug.WriteLine("Received verification request. Verifying.");
						}

						else Abort(new ExtendedSocketException("Peer out of sync"));
					}

					else if (msg.Error) Abort(new ExtendedSocketException("Unknown ID"));
				}

				else if (state == State.P_SWAP)
				{

					if (msgState != null && msgState.stage == STUNTMessage.Stage.STUNT_SWAP)
					{
						asock = GetNBConnSock(null, responders[0]);
						state = State.A_ST1;
						isActive = true;
						Debug.WriteLine("Attempting connction as active client.");
					}
				}

				else if (state == State.A_SWAP)
				{

					if (msgState != null && msgState.stage == STUNTMessage.Stage.STUNT_INVITE)
					{
						state = State.P_START;
						peerState = msgState;
						isActive = false;
						Debug.WriteLine("Attempting connection as passive client.");
					}
				}

				else
				{
					// Ideally, this should not happen. But stray messages stuck in
					// the network show up now and then. Log them.
					Debug.WriteLine("Unhandled message: " + msg);
				}
			}
		}

		#endregion

		#region Step

		public int Step(object o, Dispatch d)
		{
			Debug.WriteLine("STUNTCont - Step" + o.ToString());

			if (state == State.STOP)
			{
				// this better not happen! It would indicate some serious
				// bug in the event dispatch mechanism! Ugh!
				Debug.WriteLine(new Exception().StackTrace);
			}

			try
			{

				if (o is ISignalingMessage) HandleProxyMsg((ISignalingMessage)o);

				else if (o is Socket) HandleSocketOp((Socket)o);

				else HandleTimeout();
			}

			catch (InvalidOperationException e)
			{
				// A non-fatal error occurred. Request a SWAP and re-sync.
				// I hate using exxceptions for control flow; perhaps when I get time to
				// do a clean rewriteeee this will be fixed.
				Debug.WriteLine("STUNTCont + Step" + e.Message);
				Reset();

				if (++swapped > 2 * MAX_ATTEMPTS - 1)
				{

					try { Send(stuntState.Abort()); }

					catch (Exception i) { }
					state = State.STOP;
				}

				else
				{

					try { Send(stuntState.Swap()); }

					catch (Exception i) { }
					state = isActive ? State.A_SWAP : State.P_SWAP;
				}
			}

			catch (Exception e)
			{
				// fata error. Jump ship.
				Debug.WriteLine("STUNTCont - step" + e.Message);
				state = State.STOP;

				try { Send(stuntState.Abort()); }

				catch (Exception i) { }
				Reset();
			}
			Debug.WriteLine("STUNTCont - Step" + state + "(" + state.Timeout + ":" + tries + "/" + state.Retries + ")");
			return state.Timeout;
		}

		#endregion

		#region Cancel

		public void Cancel(Dispatch d)
		{

			if (established)
			{

				try
				{

					// give the user the socket in the nice, clean, blocking
					// state it was in when it was born
					if (d.Socket != null) d.Socket.Close();
					d.Socket.Blocking = true;
					d.Callback(this, fsock);
					fsock = null;
				}

				catch (Exception e)
				{
					Debug.WriteLine("STUNTCont - Cancel" + e.Message);
				}
			}

			else
			{
				d.Callback(this, reason);
			}
			Reset();
		}

		#endregion

	}
}
